//
//  TagCloudView.m
//  Task Views
//
//  Created by Chris Karr on 3/3/09.
//  Copyright 2009 Chris J. Karr. All rights reserved.
//

#import "TagCloudView.h"
#import "TagButton.h"

#import "NSButton+TextColor.h"

NSInteger areaSort (id one, id two, void * reverse)
{
	NSRect oneRect = [one rectValue];
	NSRect twoRect = [two rectValue];
	
	double oneArea = oneRect.size.width * oneRect.size.height;
	double twoArea = twoRect.size.width * twoRect.size.height;	

	if (oneArea > twoArea)
		return NSOrderedAscending;
	
	return NSOrderedDescending;
}

@implementation TagCloudView

- (id) initWithFrame:(NSRect) frame 
{
    self = [super initWithFrame:frame];

    if (self) 
	{
		textViews = [[NSMutableArray alloc] init];
		textDefs = [[NSArray alloc] init];
		lastView = nil;
		lastClick = nil;
    }
	
    return self;
}

- (BOOL) acceptsFirstResponder
{
	return YES;
}        

- (void) refreshTags
{
	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	NSScanner * scanner = [[NSScanner alloc] init];

	NSColor * color = [NSColor lightGrayColor];
	NSData * colorData = [defaults valueForKey:@"tag_default_color"];
	
	NSString * colorField = [defaults valueForKey:@"tag_color_attribute"];
	if (colorField == nil)
		colorField = @"name";
	
	NSDictionary * mappings = [defaults objectForKey:@"color_mappings"];
	if (mappings == nil)
		mappings = [NSDictionary dictionary];
	
	NSArray * colorStrings = [mappings allKeys];

	if (colorData != nil)
		color = [NSKeyedUnarchiver unarchiveObjectWithData:colorData];
	
	for (TagButton * field in textViews)
	{
		[field setTitle:[field.task valueForKey:@"name"]];
		
		[field setMenu:[self menu]];

		if (field.color == nil)
		{
			NSObject * value = [field.task valueForKey:colorField];
			
			if (value != nil)
			{
				NSString * superString = [value description];

				NSColor * myColor = nil;
			
				for (NSString * match in colorStrings)
				{
					if (myColor == nil)
					{
						if ([superString rangeOfString:match].location != NSNotFound)
							myColor = [NSKeyedUnarchiver unarchiveObjectWithData:[mappings valueForKey:match]];
					}
				}
				
				if (myColor != nil)
					field.color = myColor;
			}

			if (field.color == nil)
				field.color = color;
		}

		[field setTextColor:field.color];

		[field setBordered:NO];
		
		NSString * key = [defaults valueForKey:@"tag_size_attribute"];
		NSObject * value = [field.task valueForKey:key];
		NSNumber * base = nil;
		if ([value isKindOfClass:[NSNumber class]])
			base = (NSNumber *) value;
		else if ([value isKindOfClass:[NSDate class]])
		{
			NSTimeInterval dateDelta = ((([((NSDate *) value) timeIntervalSinceNow] / 60) / 60) / 24);
			base = [NSNumber numberWithDouble:(0 - dateDelta)];
		}
		else
		{
			double d;
			if (![scanner scanDouble:&d])
				d = 1;
			base = [NSNumber numberWithDouble:d];
		}
		
		NSNumber * multiplier = [defaults valueForKey:@"tag_size_multiplier"];
		if (multiplier == nil)
			multiplier = [NSNumber numberWithInteger:0];

		NSNumber * adjustment = [defaults valueForKey:@"tag_size_adjustment"];
		if (adjustment == nil)
			adjustment = [NSNumber numberWithInteger:0];
		
		float baseFloat = [base floatValue];
		
		if ([defaults boolForKey:@"tag_size_ln"])
		{
			float absLog = log(abs(baseFloat));
			
			if (baseFloat < 0)
				baseFloat = 0 - absLog;
			else
				baseFloat = absLog;
		}
		
		float size = (baseFloat * [multiplier floatValue]) + [adjustment floatValue];
		
		if (size < 0)
			size = 0;
		
		size = 6 + floorf(size);
		
		NSFont * font = [NSFont fontWithName:@"Helvetica" size:size];
		
		if (![font isEqual:[field font]])
		{	
			finishRefresh = YES;
			[field setFont:font];
		}
		
		[field sizeToFit];
		
		[field setAction:@selector(click:)];
		[field setTarget:self];
		[field setNeedsDisplay:YES];
	}
	
	[scanner release];
	
	[self setNeedsDisplay:YES];
}

- (void) refresh:(NSTimer *) theTimer
{
	if (finishRefresh)
	{
		[self refreshTags];
		
		finishRefresh = NO;
	}
}

- (void) click:(id) sender
{
	if ([sender isMemberOfClass:[TagButton class]])
	{
		TagButton * button = (TagButton *) sender;
		
		if (sender == lastClick)
		{
			[taskManager editTask:sender];
		}
		else
		{
			if (lastClick != nil)
				[lastClick setTextColor:((TagButton *) lastClick).color];
			
			[button setTextColor:[NSColor whiteColor]];
			
			[tasks setSelectedObjects:[NSArray array]];
			[tasks setSelectedObjects:[NSArray arrayWithObject:button.task]];

			lastClick = sender;
		}
	}
}

- (void) setTextDefinitions:(NSArray *) defs
{
	lastClick = nil;
	[textDefs release];
	textDefs = [defs retain];

	NSEnumerator * viewIter = [textViews objectEnumerator];
	NSView * view;
	while (view = [viewIter nextObject])
		[view removeFromSuperview];
	
	[textViews removeAllObjects];

	NSEnumerator * defIter = [textDefs objectEnumerator];
	NSDictionary * def = nil;

	srand([[NSDate date] timeIntervalSince1970]);
	
	while (def = [defIter nextObject])
	{
		TagButton * text = [[TagButton alloc] init];
		[text setHidden:YES];
		[[text cell] setShowsStateBy:NSNoCellMask];
		[[text cell] setHighlightsBy:NSNoCellMask];
		
		text.task = def;

		[textViews addObject:text];

		if (lastView == nil)
			[self addSubview:text];
		else
			[self addSubview:text positioned:NSWindowBelow relativeTo:lastView];
		
		lastView = text;
		
		[text release];
	}
	
	[self refreshTags];
}

- (void) awakeFromNib
{
	[[self window] setAcceptsMouseMovedEvents:YES];
	
	[tasks addObserver:self forKeyPath:@"arrangedObjects" options:0 context:NULL];
	[tasks addObserver:self forKeyPath:@"selection" options:0 context:NULL];

	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	
	[defaults addObserver:self forKeyPath:@"tag_size_ln" options:0 context:NULL];
	[defaults addObserver:self forKeyPath:@"tag_size_attribute" options:0 context:NULL];
	[defaults addObserver:self forKeyPath:@"tag_size_multiplier" options:0 context:NULL];
	[defaults addObserver:self forKeyPath:@"tag_size_adjustment" options:0 context:NULL];

	[defaults addObserver:self forKeyPath:@"tag_default_color" options:0 context:NULL];
	[defaults addObserver:self forKeyPath:@"tag_color_attribute" options:0 context:NULL];
	[defaults addObserver:self forKeyPath:@"color_mappings" options:0 context:NULL];

	[defaults addObserver:self forKeyPath:@"cloud_background" options:0 context:NULL];
	
	timer = [[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(refresh:) userInfo:nil repeats:YES] retain];
}

- (void) observeValueForKeyPath: (NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context
{
	if (lastClick != nil)
		[((TagButton *) lastClick) setTextColor:((TagButton *) lastClick).color];

	lastClick = nil;

	if ([keyPath isEqual:@"arrangedObjects"])
		[self setTextDefinitions:[tasks arrangedObjects]];
	else if ([keyPath isEqual:@"selection"])
	{
		NSArray * selectedTasks = [tasks selectedObjects];

		if ([selectedTasks count] > 0)
		{

			for (TagButton * field in textViews)
			{
				if (![field isHidden] && [field.task isEqualToDictionary:[[tasks selectedObjects] lastObject]])
				{
					[field setTextColor:[NSColor whiteColor]];
					
					lastClick = field;
				}
			}
		}
	}
	else
		[self setTextDefinitions:[tasks arrangedObjects]];
}

- (IBAction) refreshView:(id) sender
{
	[self setTextDefinitions:[tasks arrangedObjects]];
}
		
- (NSValue *) bestRectInValues:(NSArray *) values forSize:(NSSize) size
{
	if ([values count] == 0)
		return nil;
	
	for (NSValue * val in [values sortedArrayUsingFunction:areaSort context:NULL])
	{
		NSRect rect = [val rectValue];
		
		if (rect.size.width >= size.width && rect.size.height >= size.height)
			return val;
	}
	
	return nil;
}

- (NSArray *) shatterRect:(NSRect) container withRect:(NSRect) hole
{
	NSMutableArray * remainders = [NSMutableArray array];

	NSSize wide = NSMakeSize(container.size.width, ((container.size.height - hole.size.height) / 2));
	NSSize tall = NSMakeSize(((container.size.width - hole.size.width) / 2), container.size.height);

	if ((wide.width * wide.height) < (tall.width * tall.height)) // Use tall blocks
	{
		NSRect left = NSMakeRect(container.origin.x, container.origin.y, tall.width, tall.height);
		NSRect right = NSMakeRect((container.origin.x + container.size.width) - tall.width, container.origin.y, 
								  tall.width, tall.height);
		NSRect bottom = NSMakeRect(container.origin.x + tall.width, container.origin.y, 
								   hole.size.width, ((container.size.height - hole.size.height) / 2));
		NSRect top = NSMakeRect(container.origin.x + tall.width, hole.origin.y + hole.size.height, 
								   hole.size.width, ((container.size.height - hole.size.height) / 2));
		
		if (left.size.width > 5 && left.size.height > 5)
			[remainders addObject:[NSValue valueWithRect:left]];
		if (right.size.width > 5 && right.size.height > 5)
			[remainders addObject:[NSValue valueWithRect:right]];
		if (top.size.width > 5 && top.size.height > 5)
			[remainders addObject:[NSValue valueWithRect:top]];
		if (bottom.size.width > 5 && bottom.size.height > 5)
			[remainders addObject:[NSValue valueWithRect:bottom]];
	}
	else
	{
		NSRect bottom = NSMakeRect(container.origin.x, container.origin.y, wide.width, wide.height);
		NSRect top = NSMakeRect(container.origin.x, hole.origin.y + hole.size.height, wide.width, wide.height);
		NSRect left = NSMakeRect(container.origin.x, container.origin.y + wide.height, 
								 ((container.size.width - hole.size.width) / 2), hole.size.height);
		NSRect right = NSMakeRect(hole.origin.x + hole.size.width, left.origin.y, 
								  ((container.size.width - hole.size.width) / 2), hole.size.height);
		
		[remainders addObject:[NSValue valueWithRect:left]];
		[remainders addObject:[NSValue valueWithRect:right]];
		[remainders addObject:[NSValue valueWithRect:top]];
		[remainders addObject:[NSValue valueWithRect:bottom]];
	}
	
	return remainders;
}

- (void) drawBox:(NSRect) rect
{
	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	NSData * color = [defaults valueForKey:@"cloud_background"];
	
	if (color == nil)
		[[NSColor blackColor] setFill];
	else
		[[NSKeyedUnarchiver unarchiveObjectWithData:color] setFill];
		
	NSBezierPath * path = [NSBezierPath bezierPathWithRect:rect];
		
	[path fill];
}

- (void) drawRect:(NSRect) rect 
{
	NSSize size = [self bounds].size;
	
	[self drawBox:rect];

	if (size.width == rect.size.width && size.height == rect.size.height)
	{
		[self willChangeValueForKey:@"tasksVisible"];
		NSMutableArray * rects = [NSMutableArray arrayWithObject:[NSValue valueWithRect:[self bounds]]];

		NSEnumerator * textIter = [textViews objectEnumerator];
		NSTextField * text = nil;
		
		tasksVisible = 0;
		
		while (text = [textIter nextObject])
		{
			[text setHidden:YES];

			[text sizeToFit];
				
			NSSize size = [text frame].size;
				
			NSValue * bestValue = [self bestRectInValues:rects forSize:size];
				
			if (bestValue != nil)
			{
				[text setHidden:NO];
				
				NSRect bestRect = [bestValue rectValue];
			
				NSPoint center = NSMakePoint((bestRect.origin.x + (bestRect.size.width / 2)), 
											 (bestRect.origin.y + (bestRect.size.height / 2)));

				[text setFrameOrigin:NSMakePoint(center.x - (size.width / 2), center.y - (size.height / 2))];
		
				NSArray * remainders = [self shatterRect:bestRect withRect:[text frame]];
				
				[rects removeObject:bestValue];
				[rects addObjectsFromArray:remainders];
					
				tasksVisible = tasksVisible + 1;
			}
		}

		[self didChangeValueForKey:@"tasksVisible"];
		
		[[self window] update];
	}
	
	[super drawRect:rect];
}

- (NSNumber *) tasksVisible
{
	return [NSNumber numberWithUnsignedInteger:tasksVisible];
}

- (IBAction) settings:(id) sender
{
	[NSApp beginSheet:settings modalForWindow:[self window] modalDelegate:self didEndSelector:nil contextInfo:NULL];
}

- (IBAction) closeSettings:(id) sender;
{
	[NSApp endSheet:settings];
	[settings orderOut:self];
}

- (IBAction) addColor:(id) sender
{
	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	
	NSString * string = [substring stringValue];
	NSColor * color = [colorWell color];
	
	NSDictionary * mappings = [defaults objectForKey:@"color_mappings"];
	
	NSMutableDictionary * newMappings = [NSMutableDictionary dictionaryWithDictionary:mappings];
	[newMappings setValue:[NSKeyedArchiver archivedDataWithRootObject:color] forKey:string];
	
	[defaults setObject:newMappings forKey:@"color_mappings"];
}

@end
